産業プロトコル Modbus RTU 対応の温湿度トランスミッタから Python で温湿度データを取得してみた
最近は、Modbus や OPC-UA といった産業プロトコルを用いたデータ収集の仕組みに興味があり、Modbus 対応センサーを入手したので試してみました。
Modbus プロトコルとは?
専門分野ではないので簡単に説明すると、工場などの製造現場で使われる産業機器同士の通信規格の一つです。Modbus の他にも EtherCAT や PROFINET など多くのプロトコルが存在します。
また、Modbus には大まかに下記の3種類が存在します。
- Modbus RTU
- Modbus ASCII
- Modbus/TCP
このうち今回は「Modbus RTU」を使います。
利用デバイス
今回試してみたのは、下記の「XY-MD02」という Modbus RTU 対応センサーです。安価で入手できるのでオススメです。
注意点としては、全く同じ見た目で仕様が異なる「WTR10-E」というModbus RTU 対応センサーがあります。
見た目の違いはラベルの有無くらいですが「XY-MD02」をスリットから覗くと「XY-MD02」と書かれた青い基盤が見えます。
一方で「WTR10-E」は上記の Amazon.com の商品写真にもあるように基板からして異なります。こちらは動作時に LED が点灯するようですが「XY-MD02」は光りません。
どちらも温度と湿度(相対湿度)が取れますが、結線するコネクタの位置が異なっているなど「別物」と考えておいた方がいいようです。
実は「WTR10-E」も発注済みですが、手元に届くまでしばらくかかりそうなので入手でき次第改めてご紹介したいと思います。
用意したもの一覧
全体で必要なものは下記の通りです。
品名 | 内容 | 購入リンク | 備考 |
---|---|---|---|
Modbusセンサー | XY-MD02 温湿度トランスミッタ | https://www.amazon.co.jp/dp/B08BJ5CPRD | WTR10-Eと間違えないよう注意 |
RS485変換アダプタ | RS485からUSBに変換するアダプタ | https://www.amazon.co.jp/dp/B00GWEGZOI/ | |
ジャンパワイヤ | センサー、アダプタ、電源をつなぐワイヤ | https://www.switch-science.com/catalog/620/ | 「オス-オス」を使います |
バッテリーホルダー | リード線付きバッテリーホルダー | https://www.amazon.co.jp/gp/product/B083QGC16B/ | 6個タイプを使います |
乾電池 | 電源用の乾電池 | お近くの100均ショップなど | 単3電池 x6 本 |
購入リンクを掲載しておきますが、商品によっては「WTR10-E」を買ったのに「XY-MD02」が届いた、といったことが発生する可能性があります。
(実際に経験したのですが、アマゾンの場合は商品間違いとして返品できるので改めて再注文してください)
また、電源ですがデバイスのラベルにも書かれているように「DC 5V〜30V」が必要となります。必要な容量を確保できる電池ホルダーを準備してください。
つなげてみた
実際に繋げてみた図がこちらです。デバイス側に接続先が書かれているので迷うことはないと思います。
ラベルに書かれている通り、電池のプラス/マイナスをつなげます。RS485 は変換アダプタにも「A」「B」の記載があるので同じもの同士を接続します。
ちょっと分かりづらいですが、変換アダプタの基板にも「A」「B」と書かれています。
これをPCに接続すれば完成です。(USBを挿すだけです)
データ取得のクエリ
次にデータの取得方法を確認しておきます。Modbus でデータを取得する場合は、取得したいデータに合わせてコマンドを組んで送信します。
コマンドの書式は次の様になります。
[ID] [Function Code] [start Address] [data size]
具体的には下記の様になります。
- ID
- デバイスIDです。今回は
01
になります。
- デバイスIDです。今回は
- Function Code
- データの種類に応じて指定します。
- データを参照する場合は
03
または04
を指定します。 03
は参照のみで、04
は参照と変更が可能です。
- start adress
- データを読み取る開始位置です。
- data size
- 読み取るデータサイズです。
これだけでは何のことかサッパリかもしれませんが、今回のデバイスは下記のドキュメントがとても参考になりました。このドキュメントを何度も読み返して理解を深める事ができました。
特に下記の表が参考になりました。
下側の表を見ると、温度と湿度が知りたい場合はRegister Type
がInput Regsister
であることが分かります。(Input Registerは「Function Code 04」です)
Register Type
がKeep Register
となっているものは「Function Code 03」 のことですが、この表を見るとTemperture Correction(温度補正)
,Humidity Correction(湿度補正)
という記載なので、このデバイスは Function Code 03 ではデータが取得できないようです。(実際に試してみましたがエラーになりました)
さらに詳しく知りたい方は、下記のドキュメントも参考になるかもしれません。(専門の方からすればもっと良い資料があるかもしれません。)
動作確認
ここまでできたらPCに接続して簡単に確認してみたくなりますよね。下記のようなWindows 向けの GUI ツールがあるので試してみたかったのですが、手元の Windows マシン(Windows 10)ではドライバが自動でインストールされず使えませんでした。
(それらしいドライバもいくつか見つかったのですが、今回はこのツールは使わないことにしました)
実際のコード
GUIアプリが使えなかったのでコードで確認することにします。今回は、このデバイスを Mac に接続して試してみました。Mac の環境は以下のとおりです。
% sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H524 % python -V Python 3.8.7
また、Python で Modbus で通信するためにpymodbus
というモジュールを使います。
pip install pymodbus
実際のコードは下記です。
from pymodbus.client.sync import ModbusSerialClient as ModbusClient import logging FORMAT = ('%(asctime)-15s %(threadName)-15s ' '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') logging.basicConfig(format=FORMAT) log = logging.getLogger() log.setLevel(logging.DEBUG) def run_sync_client(): client = ModbusClient(baudrate=9600, port="/dev/tty.usbserial-14140", method="rtu") client.connect() log.debug("===Reading Input Registers===") ### read_input_registers ### rr = client.read_input_registers(address=1, count=2, unit=0x1) log.debug(rr) temperature = rr.registers[0]/10 print(f"Temperatur:\t{temperature} ℃") humidity = rr.registers[1]/10 print(f"Humidity:\t{humidity} %") client.close() if __name__ == "__main__": run_sync_client()
11行目のport="/dev/tty.usbserial-14140"
の箇所ですが、ここは USB の RS485 変換アダプタを差す度に変わるので、お使いのマシンに応じて変更してください。
下記のコマンドを USB を挿す前後で確認することで、対象のポートを把握することができると思います。
ls -l /dev/tty.*
また、先程見たようにこのデバイスではInput Regsister
、つまり「Function Code 04」でデータが取得できるようなので、コード中でもread_input_registers()
を使っています。
Input Regsister()
の引数ですが、先程のドキュメントの表にあったように、温度と湿度のデータを取りたい場合は「温度の開始アドレス(Regsiter Address)」である「0x0001」を指定します。 (address=1
)
今回は、温度と湿度のデータの両方を取得したいので、取得したいRegister Address
は「0x0001〜0x0002」までになります。そのためcount=2
としています。
温度と湿度を確認してみる
このコードを実行すると下記のようなログと、実際の温度・湿度が表示されます。
2021-08-12 05:11:01,420 MainThread DEBUG mysample4 :19 ===Reading Holding Registers=== 2021-08-12 05:11:01,421 MainThread DEBUG transaction :139 Current transaction state - IDLE 2021-08-12 05:11:01,421 MainThread DEBUG transaction :144 Running transaction 1 2021-08-12 05:11:01,421 MainThread DEBUG transaction :272 SEND: 0x1 0x4 0x0 0x1 0x0 0x2 0x20 0xb 2021-08-12 05:11:01,421 MainThread DEBUG sync :76 New Transaction state 'SENDING' 2021-08-12 05:11:01,421 MainThread DEBUG transaction :286 Changing transaction state from 'SENDING' to 'WAITING FOR REPLY' 2021-08-12 05:11:01,572 MainThread DEBUG transaction :374 Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY' 2021-08-12 05:11:01,572 MainThread DEBUG transaction :296 RECV: 0x1 0x4 0x4 0x1 0x1b 0x2 0x20 0x8a 0xc7 2021-08-12 05:11:01,572 MainThread DEBUG rtu_framer :185 Getting Frame - 0x4 0x4 0x1 0x1b 0x2 0x20 2021-08-12 05:11:01,573 MainThread DEBUG factory :266 Factory Response[ReadInputRegistersResponse: 4] 2021-08-12 05:11:01,573 MainThread DEBUG rtu_framer :107 Frame advanced, resetting header!! 2021-08-12 05:11:01,573 MainThread DEBUG transaction :453 Adding transaction 1 2021-08-12 05:11:01,573 MainThread DEBUG transaction :464 Getting transaction 1 2021-08-12 05:11:01,573 MainThread DEBUG transaction :224 Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE' 2021-08-12 05:11:01,573 MainThread DEBUG mysample4 :31 ReadInputRegistersResponse (2) Temperatur: 28.3 ℃ Humidity: 54.4 %
湿度はややズレがあるようですが、温度は概ね正確な値を取得できていました。(部屋の冷房設定は28℃)
最後に
簡単なスクリプトを使って Modbus でセンサーデータを取得することができました。
次はこれを Greengrass V2 で試していきたいと思います。